/*
   This file is part of the KDE project

   Copyright (C) 2018-2019 Jan Grulich <jgrulich@redhat.com>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "xdpevents.h"

#include "rfbservermanager.h"

#include <linux/input.h>

#include <QApplication>
#include <QDesktopWidget>
#include <QGlobalStatic>

#include <xkbcommon/xkbcommon.h>

#include "xdp/waylandintegration.h"
#include "pw_framebuffer.h"
#include "debug.h"

class EventData
{
public:
    EventData();

    //keyboard
    signed char modifiers[0x100] = {};
    uint32_t keycodes[0x100] = {}; /* level 0 */
    uint32_t keycodes2[0x100] = {}; /* level 1 */
    uint32_t leftShiftCode = 0;
    uint32_t rightShiftCode = 0;
    struct xkb_context *xkb_ctx = nullptr;
    struct xkb_keymap *xkb_keymap = nullptr;
    struct xkb_state *xkb_state = nullptr;

    //mouse
    int buttonMask = 0;
    int x = 0;
    int y = 0;

private:
    void init();
};

Q_GLOBAL_STATIC(EventData, data)

EventData::EventData()
{
    init();
}

void EventData::init()
{
    xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
    struct xkb_rule_names names = {}; /* the default keymap */
    names.layout = "us";
    names.model = "pc104";
    xkb_keymap = xkb_keymap_new_from_names(xkb_ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS);
    xkb_state = xkb_state_new(xkb_keymap);

    xkb_keycode_t min_code, max_code;

    min_code = xkb_keymap_min_keycode(xkb_keymap);
    max_code = xkb_keymap_max_keycode(xkb_keymap);

    for (auto key = min_code; key <= max_code; ++key) {
        const xkb_keysym_t *syms = NULL;
        int n = xkb_keymap_key_get_syms_by_level(xkb_keymap, key, 0, 0, &syms);
        if (n && syms) {
            keycodes[key] = syms[0]; // this is level 0 (without shift)
            for (int i = 0; i < n; i++) {
                char buf[1024];
                xkb_keysym_get_name(syms[i], buf, 1024);
                qInfo() << "1------- n:key:sym:buf" << n << key << syms[i] << buf;
            }
        }
        n = xkb_keymap_key_get_syms_by_level(xkb_keymap, key, 0, 1, &syms);
        if (n && syms) {
            keycodes2[key] = syms[0]; // this is level 1 (with shift)
            for (int i = 0; i < n; i++) {
                char buf[1024];
                xkb_keysym_get_name(syms[i], buf, 1024);
                qInfo() << "2------- n:key:sym:buf" << n << key << syms[i] << buf;
            }
        }
    }
}

void XdpEventHandler::handleKeyboard(bool down, rfbKeySym keySym)
{
    const uint streamNodeId = frameBuffer()->customProperty(QStringLiteral("stream_node_id")).toUInt();

    if (streamNodeId == 0) {
        return;
    }

    char buf[128];
    xkb_keysym_get_name(keySym, buf, 128);
    qCInfo(KRFB) << "------- " << __func__ << keySym << buf << (down?"down":"release");

    xkb_keycode_t key = 0;
    for (xkb_keycode_t i = 0; i < 0x100; i++) {
        if (keySym == data->keycodes[i]) {
            key = i;
            break;
        }
    }
    for (xkb_keycode_t i = 0; i < 0x100; i++) {
        if (keySym == data->keycodes2[i]) {
            key = i;
            break;
        }
    }
    if (key == 204) {
        key = 64;
    }
    if (key == 206) {
        key = 133;
    }

    xkb_state_update_key(data->xkb_state, key, static_cast<xkb_key_direction>(down));

    key -= 8; // convert to libinput keycode
    // data->XdpRemoteDesktopSession->NotifyKeyboardKeycode(QVariantMap(),
    // key, (down ? 1: 0));
    WaylandIntegration::requestKeyboardKeycode(key, down);
}

void XdpEventHandler::handlePointer(int buttonMask, int x, int y)
{
    const uint streamNodeId = frameBuffer()->customProperty(QStringLiteral("stream_node_id")).toUInt();
    if (streamNodeId == 0) {
        return;
    }

    if (x != data->x || y != data->y) {
        // data->XdpRemoteDesktopSession->NotifyPointerMotionAbsolute(QVariantMap(), streamNodeId, x, y);
        WaylandIntegration::requestPointerMotionAbsolute(QPointF(x, y));
        data->x = x;
        data->y = y;
    }

    if (buttonMask != data->buttonMask) {
        int i = 0;
        QVector<int> buttons = {BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, 0, 0, 0, 0, BTN_SIDE, BTN_EXTRA};
        for (auto it = buttons.constBegin(); it != buttons.constEnd(); ++it) {
            int prevButtonState = (data->buttonMask >> i) & 0x01;
            int currentButtonState = (buttonMask >> i) & 0x01;

            if (prevButtonState != currentButtonState) {
                if (*it) {
                    // data->XdpRemoteDesktopSession->NotifyPointerButton(QVariantMap(), *it, buttonMask);
                    if (buttonMask) {
                        WaylandIntegration::requestPointerButtonPress(*it);
                    } else {
                        WaylandIntegration::requestPointerButtonRelease(*it);
                    }
                } else {
                    int axis = 0;
                    int steps = 0;
                    switch (i) {
                    case 3:
                        axis = 0; // Vertical
                        steps = -1;
                        break;
                    case 4:
                        axis = 0; // Vertical
                        steps = 1;
                        break;
                    case 5:
                        axis = 1; // Horizontal
                        steps = -1;
                        break;
                    case 6:
                        axis = 1; // Horizontal
                        steps = 1;
                        break;
                    }

                    // data->XdpRemoteDesktopSession->NotifyPointerAxisDiscrete(QVariantMap(), axis, steps);
                    WaylandIntegration::requestPointerAxisDiscrete(!axis ? Qt::Vertical : Qt::Horizontal, steps);
                }
            }
            ++i;
        }
        data->buttonMask = buttonMask;
    }
}
